use crate::types::{DecodeError, Error, FieldError};
use chrono::{DateTime, Utc};
use serde_derive::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListMultiStatus {
    #[serde(rename = "d:response")]
    pub responses: Vec<ListResponse>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResponse {
    #[serde(rename = "d:href")]
    pub href: String,
    #[serde(rename = "d:propstat")]
    pub prop_stat: Vec<ListPropStat>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPropStat {
    #[serde(rename = "d:status")]
    pub status: String,
    #[serde(rename = "d:prop")]
    pub prop: ListProp,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ListResourceType {
    #[serde(rename = "d:collection")]
    pub collection: Option<()>,
    #[serde(rename = "d:redirectref")]
    pub redirect_ref: Option<()>,
    #[serde(rename = "d:redirect-lifetime")]
    pub redirect_lifetime: Option<()>,
    #[serde(rename = "d:addressbook", default)]
    pub address_book: Option<()>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListProp {
    #[serde(rename = "d:getlastmodified", deserialize_with = "http_time", default)]
    pub last_modified: Option<DateTime<Utc>>,
    #[serde(rename = "d:resourcetype", default)]
    pub resource_type: ListResourceType,
    #[serde(
        rename = "d:quota-used-bytes",
        deserialize_with = "empty_number",
        default
    )]
    pub quota_used_bytes: Option<i64>,
    #[serde(
        rename = "d:quota-available-bytes",
        deserialize_with = "empty_number",
        default
    )]
    pub quota_available_bytes: Option<i64>,
    #[serde(rename = "d:getetag")]
    pub tag: Option<String>,
    #[serde(
        rename = "d:getcontentlength",
        deserialize_with = "empty_number",
        default
    )]
    pub content_length: Option<i64>,
    #[serde(rename = "d:getcontenttype")]
    pub content_type: Option<String>,
    #[serde(rename = "calendar-data")]
    pub calendar_data: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ListEntity {
    File(ListFile),
    Folder(ListFolder),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListFile {
    pub href: String,
    pub last_modified: DateTime<Utc>,
    pub content_length: i64,
    pub content_type: String,
    pub tag: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListFolder {
    pub href: String,
    pub last_modified: DateTime<Utc>,
    pub quota_used_bytes: Option<i64>,
    pub quota_available_bytes: Option<i64>,
    pub tag: Option<String>,
    pub address_book: bool,
}

fn status_is_ok(status: &str) -> bool {
    let code = status.split_whitespace().nth(1);

    match code {
        Some(code) => code.starts_with("2"),
        None => false,
    }
}

impl From<crate::types::Error> for napi_ohos::Error {
    fn from(err: crate::types::Error) -> Self {
        napi_ohos::Error::new(
            napi_ohos::Status::GenericFailure,
            format!("Custom error: {:?}", err),
        )
    }
}

impl TryFrom<ListResponse> for ListEntity {
    type Error = crate::types::Error;
    fn try_from(response: ListResponse) -> Result<Self, Self::Error> {
        let valid_prop_stat = response
            .prop_stat
            .into_iter()
            .filter(|prop_stat| status_is_ok(&prop_stat.status))
            .next();

        match valid_prop_stat {
            Some(ListPropStat { prop, .. }) if prop.resource_type.collection.is_some() => {
                Ok(ListEntity::Folder(ListFolder {
                    href: response.href,
                    last_modified: if let Some(last_modified) = prop.last_modified {
                        last_modified
                    } else {
                        let naive_date = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
                        let naive_date_date_time = naive_date.and_hms_opt(0, 0, 0).unwrap();
                        DateTime::<Utc>::from_naive_utc_and_offset(naive_date_date_time, Utc)
                    },
                    quota_used_bytes: prop.quota_used_bytes,
                    quota_available_bytes: prop.quota_available_bytes,
                    tag: prop.tag,
                    address_book: prop.resource_type.address_book.is_some(),
                }))
            }
            Some(ListPropStat { prop, .. })
                if prop.resource_type.redirect_ref.is_some()
                    || prop.resource_type.redirect_lifetime.is_some() =>
            {
                Err(Error::Decode(DecodeError::FieldNotSupported(FieldError {
                    field: "redirect_ref".to_owned(),
                })))
            }
            Some(ListPropStat { prop, .. }) => Ok(ListEntity::File(ListFile {
                href: response.href,
                last_modified: prop.last_modified.ok_or_else(|| {
                    Error::Decode(DecodeError::FieldNotFound(FieldError {
                        field: "last_modified".to_owned(),
                    }))
                })?,
                content_length: prop.content_length.unwrap_or(0),
                content_type: prop.content_type.unwrap_or("".to_string()),
                tag: prop.tag,
            })),
            None => Err(Error::Decode(DecodeError::FieldNotFound(FieldError {
                field: "propstat with valid status".to_owned(),
            }))),
        }
    }
}

fn http_time<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let value: Option<String> = serde::Deserialize::deserialize(d)?;

    match value {
        None => Ok(None),
        Some(value) if value.is_empty() => Ok(None),
        Some(value) => match httpdate::parse_http_date(&value) {
            Ok(system_time) => Ok(Some(DateTime::<Utc>::from(system_time))),
            Err(_) => Err(serde::de::Error::invalid_value(
                serde::de::Unexpected::Str(&value),
                &"a valid HTTP date",
            )),
        },
    }
}

fn empty_number<'de, D>(d: D) -> Result<Option<i64>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let value: Option<String> = serde::Deserialize::deserialize(d)?;

    match value {
        None => Ok(None),
        Some(value) if value.is_empty() => Ok(None),
        Some(value) => match value.parse::<i64>() {
            Ok(number) => Ok(Some(number)),
            Err(_) => Err(serde::de::Error::invalid_value(
                serde::de::Unexpected::Str(&value),
                &"a valid number",
            )),
        },
    }
}
